Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Scala Native support #2846

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jchyb
Copy link

@jchyb jchyb commented Nov 5, 2021

Changes

  • Project structure was changed in compliance with sbt-crossproject.

  • googlecode's diffutils had to be removed, as Scala Native does not support java libraries. In its place, part of diffutils was reimplemented in scala as part of scalafmt-cli.

  • For similar reasons, the java interfaces were reimplemented in scala for Scala Native.

  • Since Scala Native does not yet support glob expressions, a translation function to regex was added, heavily based on a linked stack-overflow answer. Similarly, Scala's Process is replaced with java stdlib's ProcessBuilder, as Process uses concurrency.

  • SN's regex implementation is currently based on the re2 instead of the standard java regexes. This means there are a number of differences between them, most notably the lack of backtracking in Scala Native. To accommodate that, some regex function reimplementations were done to match the use of the existing scalafmt regexes in Scala Native.

  • Some operations concerning tests were slightly modified to work on Native. For example, renaming a file to an existing directory path could throw errors. Hopefully this is fine, as it does not concern the core of the test, only things like cleanup.

  • Assumptions about dynamic tests not working on native were added. Scala Native does not support URLs and only has basic opt-in reflection support comparable to the one of GraalVM, so the used and expected version of scalafmt must match here as well.

  • Term Display was made single-threaded for the Scala Native, as SN does not support concurrency yet. Since the new implementation caused display artifacts on JVM, the old multithreaded one was kept there.

  • Scala Native setup and testing was added to the CI. Windows support was temporarily disabled as it does not pass all of the tests yet.

  • scala-native sbt command was also added for easier scala-native cli building. Use sbt scala-native to generate Scala Native binaries (with LLVM installed).

Publishing

I did not include any publishing in this PR. For now there is not much point in releasing the scala-native binaries. Publishing as a library is another story, but I have not looked into that yet.

Performance

There are some severe performance problems when using the binaries generated from Scala Native. This should not happen. I tried to move the regex reimplementation as well as TermDisplay to use in JVM, yet that did not cause any noticeable performance problems there - unfortunately it seems that there is a bug on scala-native side. I will try to look into that issue.

Comparison running on Scalafmt repo:

JVM:

VL-D-0143:scalafmt jchyb$ time java -jar ./scalafmt-cli/jvm/target/scala-2.13/scalafmt.jar
Reformatting...
  100,0% [##########] 217 source files formatted

real    0m8.961s
user    0m43.648s
sys     0m1.467s

Native (Immix GC, release-fast optimization mode, full LTO, but I tried with many more options to no avail):

VL-D-0143:scalafmt jchyb$ time ./scalafmt-cli/native/target/scala-2.13/scalafmt-cli-out
Reformatting...
  100.0% [##########] 217 source files formatted

real    0m11.005s
user    0m15.570s
sys     0m5.000s

Additional comments

To smoothen out the review process I split this PR into two commits, one with only renames, and one with more meaningful changes. Hopefully this will help a bit :)

jchyb and others added 2 commits November 4, 2021 19:42
Project structure was changed in compliance with sbt-crossproject to
accomodate Scala Native support.

Co-authored-by: Tomasz Godzik <tgodzik@users.noreply.github.com>
Project structure was changed in compliance with sbt-crossproject.

googlecode's diffutils had to be removed, as Scala Native does not
support java libraries. In its place, part of diffutils was
reimplemented in scala as part of scalafmt-cli.

For similar reasons, the java interfaces were reimplemented in scala for
Scala Native.

Since Scala Native does not yet support glob expressions, a translation
function to regex was added, heavily based on a linked stack-overflow
answer. Similarly, Scala's Process is replaced with java stdlib's
ProcessBuilder, as Process uses concurrency.

SN's regex implementation is currently based on the re2 instead of the
standard java regexes. This means there are a number of differences
between them, most notably the lack of backtracking in Scala Native.
To accomodate that, some regex function reimplementations were done to
accomodate the use of the existing scalafmt regexes in Scala Native.

Some operations concerning tests were slightly modified to work on
Native. For example, renaming a file to an existing directory path could
throw errors. This is fine, as it does not concern the core of the test,
only things like cleanup.

Assumptions about dynamic tests not working on native were added.
Scala Native does not support URLs and only has basic reflection support
comparable to the one of GraalVM, so the used and expected version of
scalafmt must match here as well.

Term Display was made single-threaded for the Scala Native
implementation, as it does not support concurrency. Since the new
implementation caused display artifacts on JVM, the old multithreaded
one was kept there.

Scala Native setup and testing was added to the CI. The windows was
temporarily disabled as it does not pass all of the tests.

scala-native sbt command was also added for easier scala-native cli
building.

Co-authored-by: Tomasz Godzik <tgodzik@users.noreply.github.com>
@kitbellew
Copy link
Collaborator

@jchyb massive amount of work, kudos!

i like your decision to rename in a separate commit. I'd like to suggest that we split the second one into several more focused commits, such as turning projects into cross projects, adding each replacement one at a time (for diffutils etc.).

in fact, maybe even split into a couple of pull requests. reviewing 300 files is scary. 😀

@jchyb
Copy link
Author

jchyb commented Nov 5, 2021

Most of the work was honestly done by Tomasz, as was it his idea to do a separate commit. I can of course split it into further commits with files concerning separate changes later today. Personally, I would prefer keeping it all in this PR, as I fear the merging could otherwise become messy and now its easier to rebase if necessary. In case of potential review findings/concerns, I can be updating the individual commits, so that we don't have to use the now-dreaded "Files changed" tab ;)

Copy link
Collaborator

@kitbellew kitbellew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very, very incomplete review. definitely lots of changes which can be submitted and merged separately.

also, would strongly prefer that every commit result in valid, compilable, testable code and could be separated or reverted by itself.

@@ -0,0 +1,262 @@
package org.scalafmt.cli
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. file name itself contains a typo
  2. is this copied from somewhere? complex code.

@@ -81,7 +81,7 @@ To build a native image of the command-line interface using

- From the project root directory,
- run `sbt cli/assembly`
- run `java -jar scalafmt-cli/target/scala-2.13/scalafmt.jar`, to execute recently built artifacts
- run `java -jar scalafmt-cli/jvm/target/scala-2.13/scalafmt.jar`, to execute recently built artifacts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i assumed that no suffix for jvm meant perhaps that jvm wouldn't be added here...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately that's how cross building works in this case. We could probably override it for jvm?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No suffix only works for for the sbt commands I think, so f.e. in the cross project instead of cliJVM/run we only need to run cli/run for the JVM platform in this branch. The idea was to keep the previously used commands intact.

@@ -174,15 +210,6 @@ lazy val cli = project
val oldStrategy = (assembly / assemblyMergeStrategy).value
oldStrategy(x)
},
libraryDependencies ++= Seq(
"com.googlecode.java-diff-utils" % "diffutils" % "1.3.0",
"com.martiansoftware" % "nailgun-server" % "0.9.1",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this dependency remains. why not keep it here, instead pushing down?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's probably my bad, experimented a lot with the project and kind of left a mess.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i must have misunderstood what that no suffix call does. just wanted to make sure this is correct.

@@ -0,0 +1,280 @@
package org.scalafmt.cli
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nothing in common with the other version?

@@ -22,11 +22,10 @@ object ScalafmtCoreRunner extends ScalafmtRunner {
ExitCode.UnexpectedError
} { scalafmtConf =>
options.common.debug.println(s"parsed config (v${Versions.version})")
val filterMatcher = ProjectFiles.FileMatcher(
lazy val filterMatcher = ProjectFiles.FileMatcher(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed?

@@ -0,0 +1,26 @@
package org.scalafmt.interfaces
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the difference with non native version? should we simply move from Java to scala in the original?

// native - FileNotFoundException, JVM - NoSuchFileException
assert(
confError.cause.exists(err =>
err.isInstanceOf[NoSuchFileException] || err
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

break line, looks bad

@@ -104,7 +105,13 @@ class CliOptionsTest extends FunSuite {
val opt = baseCliOptions.copy(config = Some(configPath.toFile))
assert(opt.scalafmtConfig.isInstanceOf[Configured.NotOk])
val confError = opt.scalafmtConfig.asInstanceOf[Configured.NotOk].error
assert(confError.cause.exists(_.isInstanceOf[NoSuchFileException]))
// native - FileNotFoundException, JVM - NoSuchFileException
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and we can't use the same in both cases?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jchyb If you haven't already, an issue should be filed in Scala Native.

val label = if (version == Versions.version) "core" else "dynamic"
val (label, isDynamic) =
if (version == Versions.version) ("core", false) else ("dynamic", true)
def assumeNotDynamicOnNative(): Unit =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we just call this once?

@@ -70,9 +70,10 @@ class GitOpsTest extends FunSuite {
dir.orElse(ops.rootDir).get.jfile.toPath,
"dir_"
)
val destPath = destDir.resolve("new_file")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like perfect candidate for separate pr

@tgodzik
Copy link
Contributor

tgodzik commented Nov 5, 2021

Thanks @jchyb for following up with the PR and thanks @kitbellew for the review! I don't expect to have this merged right away and we should come up with a plan what are the parts that we can merge separately and ones that we can split over multiple commits.

I will talk with @jchyb how we can go about it.

@tgodzik
Copy link
Contributor

tgodzik commented Nov 5, 2021

Just thinking about it now we could split it at least into:

  • adding difflib (maybe make it a separate library)
  • adding cross project without native support (so msotly renames)
  • adding other native changes in separate commits

@kitbellew
Copy link
Collaborator

Just thinking about it now we could split it at least into:

  • adding difflib (maybe make it a separate library)

great idea (both)

  • adding cross project without native support (so msotly renames)

preparing jvm changes for native counterpart as part of that (such as regex refactors, term display, sys process etc.)

  • adding other native changes in separate commits

@He-Pin
Copy link

He-Pin commented Aug 30, 2023

is this still possible to get merged?

@tgodzik
Copy link
Contributor

tgodzik commented Aug 30, 2023

I think this requires a bit more time, which we currently don't have, so if anyone would be willing to spend some time on it that would be awesome.

@ekrich
Copy link

ekrich commented Aug 30, 2023

Getting the difflib made into a scalameta project would be great. I asked Wojciech if it might work for our uses in Scala Native too.

@He-Pin
Copy link

He-Pin commented Aug 30, 2023

I think this PR is big ,maybe can be splitter and deliver in batchs.

@ekrich
Copy link

ekrich commented Aug 30, 2023

@He-Pin Yes, that is what @kitbellew suggests above. The difflib might be able to be used by Scala Native too. Not sure the best way there - create a project for eventual adding to scalameta org? @tgodzik What do you think?

@tgodzik
Copy link
Contributor

tgodzik commented Aug 31, 2023

@He-Pin Yes, that is what @kitbellew suggests above. The difflib might be able to be used by Scala Native too. Not sure the best way there - create a project for eventual adding to scalameta org? @tgodzik What do you think?

We usually inline it as it's rather small, so we never had any will to make it a separate library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants